{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 使用重复元素的网络（VGG）\n",
    "\n",
    "AlexNet在LeNet的基础上增加了3个卷积层。但AlexNet作者对它们的卷积窗口、输出通道数和构造顺序均做了大量的调整。虽然AlexNet指明了深度卷积神经网络可以取得出色的结果，但并没有提供简单的规则以指导后来的研究者如何设计新的网络。我们将在本章的后续几节里介绍几种不同的深度网络设计思路。\n",
    "\n",
    "本节介绍VGG，它的名字来源于论文作者所在的实验室Visual Geometry Group [1]。VGG提出了可以通过重复使用简单的基础块来构建深度模型的思路。\n",
    "\n",
    "## VGG块\n",
    "\n",
    "VGG块的组成规律是：连续使用数个相同的填充为1、窗口形状为$3\\times 3$的卷积层后接上一个步幅为2、窗口形状为$2\\times 2$的最大池化层。卷积层保持输入的高和宽不变，而池化层则对其减半。我们使用`vgg_block`函数来实现这个基础的VGG块，它可以指定卷积层的数量`num_convs`和输出通道数`num_channels`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.0.0\n"
     ]
    }
   ],
   "source": [
    "import tensorflow as tf\n",
    "print(tf.__version__)\n",
    "\n",
    "for gpu in tf.config.experimental.list_physical_devices('GPU'):\n",
    "    tf.config.experimental.set_memory_growth(gpu, True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "def vgg_block(num_convs, num_channels):\n",
    "    blk = tf.keras.models.Sequential()\n",
    "    for _ in range(num_convs):\n",
    "        blk.add(tf.keras.layers.Conv2D(num_channels,kernel_size=3,\n",
    "                                    padding='same',activation='relu'))\n",
    "    \n",
    "    blk.add(tf.keras.layers.MaxPool2D(pool_size=2, strides=2))\n",
    "    return blk"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## VGG网络\n",
    "\n",
    "与AlexNet和LeNet一样，VGG网络由卷积层模块后接全连接层模块构成。卷积层模块串联数个`vgg_block`，其超参数由变量`conv_arch`定义。该变量指定了每个VGG块里卷积层个数和输出通道数。全连接模块则跟AlexNet中的一样。\n",
    "\n",
    "现在我们构造一个VGG网络。它有5个卷积块，前2块使用单卷积层，而后3块使用双卷积层。第一块的输出通道是64，之后每次对输出通道数翻倍，直到变为512。因为这个网络使用了8个卷积层和3个全连接层，所以经常被称为VGG-11。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面我们实现VGG-11。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "def vgg(conv_arch):\n",
    "    net = tf.keras.models.Sequential()\n",
    "    for (num_convs, num_channels) in conv_arch:\n",
    "        net.add(vgg_block(num_convs,num_channels))\n",
    "    net.add(tf.keras.models.Sequential([tf.keras.layers.Flatten(),\n",
    "             tf.keras.layers.Dense(4096,activation='relu'),\n",
    "             tf.keras.layers.Dropout(0.5),\n",
    "             tf.keras.layers.Dense(4096,activation='relu'),\n",
    "             tf.keras.layers.Dropout(0.5),\n",
    "             tf.keras.layers.Dense(10,activation='sigmoid')]))\n",
    "    return net\n",
    "\n",
    "net = vgg(conv_arch)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面构造一个高和宽均为224的单通道数据样本来观察每一层的输出形状。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sequential_1 output shape:\t (1, 112, 112, 64)\n",
      "sequential_2 output shape:\t (1, 56, 56, 128)\n",
      "sequential_3 output shape:\t (1, 28, 28, 256)\n",
      "sequential_4 output shape:\t (1, 14, 14, 512)\n",
      "sequential_5 output shape:\t (1, 7, 7, 512)\n",
      "sequential_6 output shape:\t (1, 10)\n"
     ]
    }
   ],
   "source": [
    "X = tf.random.uniform((1,224,224,1))\n",
    "for blk in net.layers:\n",
    "    X = blk(X)\n",
    "    print(blk.name, 'output shape:\\t', X.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可以看到，每次我们将输入的高和宽减半，直到最终高和宽变成7后传入全连接层。与此同时，输出通道数每次翻倍，直到变成512。因为每个卷积层的窗口大小一样，所以每层的模型参数尺寸和计算复杂度与输入高、输入宽、输入通道数和输出通道数的乘积成正比。VGG这种高和宽减半以及通道翻倍的设计使得多数卷积层都有相同的模型参数尺寸和计算复杂度。\n",
    "\n",
    "## 获取数据和训练模型\n",
    "\n",
    "因为VGG-11计算上比AlexNet更加复杂，出于测试的目的我们构造一个通道数更小，或者说更窄的网络在Fashion-MNIST数据集上进行训练。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "ratio = 4\n",
    "small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch]\n",
    "net = vgg(small_conv_arch)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "x_batch shape: (128, 224, 224, 1) y_batch shape: (128,)\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "class DataLoader():\n",
    "    def __init__(self):\n",
    "        fashion_mnist = tf.keras.datasets.fashion_mnist\n",
    "        (self.train_images, self.train_labels), (self.test_images, self.test_labels) = fashion_mnist.load_data()\n",
    "        self.train_images = np.expand_dims(self.train_images.astype(np.float32)/255.0,axis=-1)\n",
    "        self.test_images = np.expand_dims(self.test_images.astype(np.float32)/255.0,axis=-1)\n",
    "        self.train_labels = self.train_labels.astype(np.int32)\n",
    "        self.test_labels = self.test_labels.astype(np.int32)\n",
    "        self.num_train, self.num_test = self.train_images.shape[0], self.test_images.shape[0]\n",
    "        \n",
    "    def get_batch_train(self, batch_size):\n",
    "        index = np.random.randint(0, np.shape(self.train_images)[0], batch_size)\n",
    "        #need to resize images to (224,224)\n",
    "        resized_images = tf.image.resize_with_pad(self.train_images[index],224,224,)\n",
    "        return resized_images.numpy(), self.train_labels[index]\n",
    "    \n",
    "    def get_batch_test(self, batch_size):\n",
    "        index = np.random.randint(0, np.shape(self.test_images)[0], batch_size)\n",
    "        #need to resize images to (224,224)\n",
    "        resized_images = tf.image.resize_with_pad(self.test_images[index],224,224,)\n",
    "        return resized_images.numpy(), self.test_labels[index]\n",
    "\n",
    "batch_size = 128\n",
    "dataLoader = DataLoader()\n",
    "x_batch, y_batch = dataLoader.get_batch_train(batch_size)\n",
    "print(\"x_batch shape:\",x_batch.shape,\"y_batch shape:\", y_batch.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "除了使用了稍大些的学习率，模型训练过程与上一节的AlexNet中的类似。\n",
    "\n",
    "注：这里省略了训练过程的输出，如果您需要进行训练，请执行train_vgg()函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 128 samples\n",
      "128/128 [==============================] - 2s 16ms/sample - loss: 2.3027 - accuracy: 0.1562\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<tensorflow.python.keras.callbacks.History at 0x18b1aa34988>"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def train_vgg():\n",
    "#     net.load_weights(\"5.7_vgg_weights.h5\")\n",
    "    epoch = 5\n",
    "    num_iter = dataLoader.num_train//batch_size\n",
    "    for e in range(epoch):\n",
    "        for n in range(num_iter):\n",
    "            x_batch, y_batch = dataLoader.get_batch_train(batch_size)\n",
    "            net.fit(x_batch, y_batch)\n",
    "            if n%20 == 0:\n",
    "                net.save_weights(\"5.7_vgg_weights.h5\")\n",
    "                \n",
    "optimizer = tf.keras.optimizers.SGD(learning_rate=0.05, momentum=0.0, nesterov=False)\n",
    "\n",
    "net.compile(optimizer=optimizer,\n",
    "              loss='sparse_categorical_crossentropy',\n",
    "              metrics=['accuracy'])\n",
    "\n",
    "x_batch, y_batch = dataLoader.get_batch_train(batch_size)\n",
    "net.fit(x_batch, y_batch)\n",
    "# train_vgg()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们将训练好的参数读入，然后取测试数据计算测试准确率"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2000/1 - 2s - loss: 0.2552 - accuracy: 0.8855\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[0.31822608125209806, 0.8855]"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "net.load_weights(\"5.7_vgg_weights.h5\")\n",
    "\n",
    "x_test, y_test = dataLoader.get_batch_test(2000)\n",
    "net.evaluate(x_test, y_test, verbose=2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 小结\n",
    "\n",
    "* VGG-11通过5个可以重复使用的卷积块来构造网络。根据每块里卷积层个数和输出通道数的不同可以定义出不同的VGG模型。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "tf2.0-gpu",
   "language": "python",
   "name": "tf2.0-gpu"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
